#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <strings.h> // For strncasecmp
#include <pthread.h> // For multi-threading

// --- Helper Functions (Same as knock.c) ---
// Error handling function
void error(char *msg)
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

// Creates the listener socket
int open_listener_socket()
{
    int s = socket(PF_INET, SOCK_STREAM, 0);
    if (s == -1)
        error("Can't open socket");
    return s;
}

// Binds the socket to the specified port and sets reuse option
void bind_to_port(int socket, int port)
{
    struct sockaddr_in name;
    name.sin_family = PF_INET;
    name.sin_port = (in_port_t)htons(port);
    name.sin_addr.s_addr = htonl(INADDR_ANY);

    int reuse = 1;
    if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(int)) == -1)
        error("Can't set the reuse option on the socket");

    if (bind(socket, (struct sockaddr *)&name, sizeof(name)) == -1)
        error("Can't bind to socket");
}

// Sends a string to the client socket
int say(int socket, char *s)
{
    int result = send(socket, s, strlen(s), 0);
    if (result == -1)
    {
        fprintf(stderr, "[Thread %lu] %s: %s\n", pthread_self(), "Error talking to the client", strerror(errno));
    }
    return result;
}

// Reads a line from the client socket (modified from PDF for simplicity)
int read_in(int socket, char *buf, int len)
{
    memset(buf, 0, len); // Clear buffer
    int bytes_received = recv(socket, buf, len - 1, 0);

    if (bytes_received < 0)
    { // Error
        fprintf(stderr, "[Thread %lu] Error reading from client: %s\n", pthread_self(), strerror(errno));
        return -1;
    }
    else if (bytes_received == 0)
    { // Connection closed
        buf[0] = '\0';
        return 0;
    }
    else
    {
        // Remove trailing \r\n if present
        if (bytes_received >= 2 && buf[bytes_received - 2] == '\r' && buf[bytes_received - 1] == '\n')
        {
            buf[bytes_received - 2] = '\0';
        }
        else if (bytes_received >= 1 && buf[bytes_received - 1] == '\n')
        {
            buf[bytes_received - 1] = '\0';
        }
        // Ensure null termination even if no newline was sent
        buf[bytes_received] = '\0';
    }
    return bytes_received;
}

// Global variable for listener socket descriptor
int listener_d;

// Signal handler for graceful shutdown (Ctrl+C)
void handle_shutdown(int sig)
{
    if (listener_d)
        close(listener_d);

    fprintf(stderr, "Bye!\n");
    exit(0);
}

// Function to set up signal handling
int catch_signal(int sig, void (*handler)(int))
{
    struct sigaction action;
    action.sa_handler = handler;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    return sigaction(sig, &action, NULL);
}

// --- Thread Function to Handle Client Connection ---
void *handle_client(void *arg)
{
    int connect_d = *(int *)arg; // Get client socket descriptor from argument
    free(arg);                   // Free the allocated memory for the argument

    // Detach the thread so resources are automatically released when it exits
    pthread_detach(pthread_self());

    char buf[255];

    // Print Thread ID
    printf("[Thread %lu] Handling connection on descriptor %d\n", pthread_self(), connect_d);

    // Start Knock-Knock protocol
    if (say(connect_d, "Internet Knock-Knock Protocol Server (MT)\r\nVersion 1.0\r\nKnock! Knock!\r\n> ") != -1)
    {
        read_in(connect_d, buf, sizeof(buf));
        printf("[Thread %lu] Received: %s\n", pthread_self(), buf);
        // Check first response
        if (strncasecmp("Who's there?", buf, 12) != 0)
        {
            say(connect_d, "You should say 'Who's there?'!\r\n");
        }
        else
        {
            // Send first part of joke
            if (say(connect_d, "Oscar\r\n> ") != -1)
            {
                read_in(connect_d, buf, sizeof(buf));
                printf("[Thread %lu] Received: %s\n", pthread_self(), buf);
                // Check second response
                if (strncasecmp("Oscar who?", buf, 10) != 0)
                {
                    say(connect_d, "You should say 'Oscar who?'!\r\n");
                }
                else
                {
                    // Send punchline
                    say(connect_d, "Oscar silly question, you get a silly answer\r\n");
                }
            }
        }
    }

    // Close the connection with this client
    printf("[Thread %lu] Closing connection on descriptor %d\n", pthread_self(), connect_d);
    close(connect_d);
    return NULL;
}

// --- Main Server Logic ---
int main(int argc, char *argv[])
{
    // Set up signal handler for Ctrl+C
    if (catch_signal(SIGINT, handle_shutdown) == -1)
        error("Can't set the interrupt handler");

    // Create listener socket
    listener_d = open_listener_socket();

    // Bind to port 30000
    bind_to_port(listener_d, 30000);

    // Start listening (queue length 10)
    if (listen(listener_d, 10) == -1)
        error("Can't listen");

    struct sockaddr_storage client_addr;
    unsigned int address_size = sizeof(client_addr);

    puts("Waiting for connections (Multi-Threaded)...");

    // Main server loop
    while (1)
    {
        // Accept a connection
        int *connect_d_ptr = malloc(sizeof(int)); // Allocate memory to pass descriptor to thread
        if (!connect_d_ptr)
        {
            perror("Failed to allocate memory for client descriptor");
            continue; // Try to accept next connection
        }

        *connect_d_ptr = accept(listener_d, (struct sockaddr *)&client_addr, &address_size);

        if (*connect_d_ptr == -1)
        {
            free(connect_d_ptr); // Free memory before erroring out or continuing
            // Don't error out the whole server for an accept error, just log it
            perror("Can't accept connection");
            continue; // Continue listening for other connections
        }

        // Create a new thread to handle the client
        pthread_t client_thread;
        if (pthread_create(&client_thread, NULL, handle_client, connect_d_ptr) != 0)
        {
            perror("Failed to create thread");
            close(*connect_d_ptr); // Close the connection if thread creation failed
            free(connect_d_ptr);   // Free memory
        }
        // Main thread continues to accept new connections
    }

    // Unreachable code
    close(listener_d);
    return 0;
}